const OperateTypes = {
  ERASE: 0,
  RECOVER: 1,
};
const ScaleChangeType = {
  ZOOMIN: 0, // 放大
  ZOOMOUT: 1, // 缩小
};
const HistoryTypes = {
  EDIT: 0,
  SCALE: 1,
};
const editItem = {
  originUrl: "./imgs/1.jpg",
  finalUrl: "./imgs/1.png",
};
const tool = {
  init: function () {
    this.initVariables();
    this.setCanvasCtx();
    this.setCursor();
    this.setScaleNum();
    this.events();
  },
  initVariables: function () {
    this.eraseSlider = document.getElementById("eraseSlider");
    this.restoreSlider = document.getElementById("restoreSlider");
    this.canvasWrapper = document.getElementById("canvasWrapper");
    this.canvas = document.getElementById("canvas");
    this.ctx = this.canvas.getContext("2d");
    this.cursor = document.getElementById("cursor");
    this.backward = document.getElementById("backward");
    this.forward = document.getElementById("forward");
    this.scaleWrapper = document.getElementById("scaleWrapper");
    this.zoomInBtn = document.getElementById("zoomIn");
    this.zoomOutBtn = document.getElementById("zoomOut");
    this.tabHeader = document.getElementById("tabHeader");
    this.tabContent = document.getElementById("tabContent");
    this.dragging = false;
    this.offscreenCanvasOri = null;
    this.offscreenContextOri = null;
    this.offscreenCanvasEdit = null;
    this.offscreenContextEdit = null;
    this.imageOri = new Image();
    this.imageFinal = new Image();

    this.imgXY = {
      x: 0,
      y: 0,
    };
    (this.maxHistoryStep = 30),
      (this.currentXY = {
        x: 0,
        y: 0,
      });
    this.mouseDown = {
      x: 0,
      y: 0,
    };
    this.lastXY = {
      x: 0,
      y: 0,
    };
    this.operateList = [
      {
        type: OperateTypes.ERASE,
        text: "擦除",
      },
      {
        type: "restore",
        text: "恢复",
      },
    ];
    this.eraseBrushSize = 24;
    this.restoreBrushSize = 24;
    this.scale = 1;
    this.history = {
      step: -1,
      operations: [],
    };
    this.dragging = false;
    this.isOver = false;
    this.activeOperate = OperateTypes.ERASE;
  },
  setScaleNum: function () {
    this.scaleWrapper.innerHTML = parseInt(this.scale * 100) + "%";
  },
  setCursor: function () {
    const brushSize =
      this.activeOperate === OperateTypes.ERASE
        ? this.eraseBrushSize
        : this.restoreBrushSize;
    this.cursor.style = `display : ${this.isOver ? "block" : "none"};
    width:${brushSize}px;
    height: ${brushSize}px; 
    border-radius: ${brushSize / 2}px;
    left: ${this.lastXY.x - brushSize / 2}px;
    top: ${this.lastXY.y - brushSize / 2}px;`;
  },
  events: function () {
    this.eraseSlider.onchange = () => {
      this.eraseBrushSize = this.eraseSlider.value;
      this.setCursor();
    };

    this.restoreSlider.onchange = () => {
      this.restoreBrushSize = this.restoreSlider.value;
      this.setCursor();
    };

    this.canvasWrapper.onmouseenter = () => {
      this.canvasMouseEnter();
    };
    this.canvasWrapper.onmouseleave = () => {
      this.canvasMouseLeave();
    };

    this.canvasWrapper.onmousedown = (e) => {
      this.canvasMouseDown(e);
    };
    this.canvasWrapper.onmousemove = (e) => {
      this.canvasMouseMove(e);
    };
    this.canvasWrapper.onmouseup = (e) => {
      this.canvasMouseUp(e);
    };

    document.onmouseup = () => {
      this.dragging = false;
      console.log("document mouse up");
    };

    // 历史记录

    this.backward.onclick = () => {
      this.stepBackward();
    };
    this.forward.onclick = () => {
      this.stepForward();
    };

    // 放大缩小
    this.zoomInBtn.onclick = () => {
      this.zoomOutBtn.className = "";
      if (this.scale >= 4) {
        this.zoomInBtn.className = "disabled";
        return false;
      }
      this.changeScale(ScaleChangeType.ZOOMIN);
      this.zoomInBtn.className = "";
    };

    this.zoomOutBtn.onclick = () => {
      this.zoomInBtn.className = "";
      if (this.scale <= 0.2) {
        this.zoomOutBtn.className = "disabled";
        return false;
      }
      this.zoomOutBtn.className = "";
      this.changeScale(ScaleChangeType.ZOOMOUT);
    };

    // tab切换
    const lis = this.tabHeader.getElementsByTagName("li");
    const contents = this.tabContent.getElementsByClassName("tab-content");
    const that = this;
    for (var i = 0; i < lis.length; i++) {
      const current = i;
      lis[i].onclick = function (e) {
        for (var k = 0; k < lis.length; k++) {
          lis[k].className = "";
        }
        e.target.className = "active";
        that.activeOperate = current;
        for (var j = 0; j < contents.length; j++) {
          contents[j].style = "display:none;";
        }
        contents[current].style = "display: block";
        that.setCursor();
      };
    }
  },

  createOffscreenCanvas: function (offscreenCanvas, offscreenContext, cb) {
    offscreenCanvas = document.createElement("canvas");
    document.getElementById("leftWrapper").appendChild(offscreenCanvas);

    if (canvas) {
      offscreenCanvas.width = canvas.width;
      offscreenCanvas.height = canvas.height;
    }

    offscreenContext = offscreenCanvas.getContext("2d");
    return {
      offscreenCanvas,
      offscreenContext,
    };
  },
  setCanvasCtx: function () {
    const offscreenOri = this.createOffscreenCanvas(
      this.offscreenCanvasOri,
      this.offscreenContextOri
    );
    this.offscreenCanvasOri = offscreenOri.offscreenCanvas;
    this.offscreenContextOri = offscreenOri.offscreenContext;

    this.setOffCanvasImage(
      this.offscreenCanvasOri,
      this.offscreenContextOri,
      this.imageOri,
      editItem.originUrl
    );

    const offscreenEdit = this.createOffscreenCanvas(
      this.offscreenCanvasEdit,
      this.offscreenContextEdit
    );
    this.offscreenCanvasEdit = offscreenEdit.offscreenCanvas;
    this.offscreenContextEdit = offscreenEdit.offscreenContext;

    this.setOffCanvasImage(
      this.offscreenCanvasEdit,
      this.offscreenContextEdit,
      this.imageFinal,
      editItem.finalUrl,
      () => {
        this.setCanvasImage();
      }
    );
  },
  addHistory: function (canvas, ctx, type) {
    console.log("addHistory");

    // 增加一条历史记录将此条后面的所有历史记录删除
    this.history.operations.length = this.history.step + 1;
    if (this.history.operations.length + 1 > this.maxHistoryStep) {
      this.history.operations.shift();
      this.history.step = this.history.step - 1;
    }
    const { scale } = this;
    const newImageData = this.offscreenContextEdit.getImageData(
      0,
      0,
      canvas.width,
      canvas.height
    );
    this.history.operations.push({
      type,
      scale,
      offscreenEditImageData: new ImageData(
        newImageData.data,
        newImageData.width,
        newImageData.height
      ),
    });
    this.history.step = this.history.step + 1;

    this.isHistoryBtnDisabled();
  },

  stepBackward: function () {
    // 向后一条
    if (this.history.step + 1 >= this.history.operations.length) {
      return false;
    }

    this.history.step += 1;
    this.isHistoryBtnDisabled();
    this.setHistoryData();
  },
  isHistoryBtnDisabled: function () {
    if (this.history.step - 1 < 0) {
      this.forward.className = "disabled";
    } else {
      this.forward.className = "";
    }
    if (this.history.step + 1 >= this.history.operations.length) {
      this.backward.className = "disabled";
    } else {
      this.backward.className = "";
    }
  },
  stepForward: function () {
    // 向前一条
    if (this.history.step - 1 < 0) {
      return false;
    }

    this.history.step -= 1;
    this.isHistoryBtnDisabled();
    this.setHistoryData();
  },
  setHistoryData: function () {
    /**
     * 1. 修改画面, 需要同步offscreenCanvasEdit
     * 2. 放大缩小，需要同步offscreenCanvasOri
     */
    const { history: newHistory } = this;

    const { type, scale, offscreenEditImageData } = newHistory.operations[
      newHistory.step
    ];
    const { scale: oldScale } = this;
    if (
      this.offscreenContextEdit &&
      this.canvas &&
      this.ctx &&
      this.offscreenContextOri &&
      this.offscreenCanvasEdit
    ) {
      const w = canvas.width,
        h = canvas.height;
      const sw = scale * w,
        sh = scale * h;

      this.imgXY = {
        x: w / 2 - sw / 2,
        y: h / 2 - sh / 2,
      };
      this.offscreenContextEdit.putImageData(offscreenEditImageData, 0, 0);
      if (oldScale !== scale) {
        this.offscreenContextOri.clearRect(0, 0, w, h);
        this.offscreenContextOri.drawImage(
          this.imageOri,
          this.imgXY.x,
          this.imgXY.y,
          sw,
          sh
        );
      }

      this.ctx.clearRect(0, 0, w, h);
      this.ctx.drawImage(
        this.offscreenCanvasEdit,
        this.imgXY.x,
        this.imgXY.y,
        sw,
        sh
      );
      this.scale = scale;
      this.setScaleNum();
    }
  },
  setCanvasImage: function () {
    this.ctx.drawImage(
      this.imageFinal,
      0,
      0,
      this.canvas.width,
      this.canvas.height
    );
    this.addHistory(this.canvas, this.ctx, HistoryTypes.EDIT);
  },

  setOffCanvasImage: function (
    offscreenCanvas,
    offscreenContext,
    image,
    url,
    cb
  ) {
    image.src = url;
    console.log(offscreenCanvas);
    image.onload = function () {
      if (offscreenContext && offscreenCanvas) {
        offscreenContext.drawImage(
          image,
          0,
          0,
          offscreenCanvas.width,
          offscreenCanvas.height
        );
      }

      cb ? cb() : null;
    };
  },

  handleChange: function (value) {
    const size = value;
    this.activeOperate === OperateTypes.ERASE
      ? (this.eraseBrushSize = size)
      : (this.restoreBrushSize = size);

    this.setCursor();
  },
  changeTab: function (value) {
    this.activeOperate = value;
    this.setCursor();
  },
  windowToCanvas: function (x, y) {
    const canvasBox = canvas
      ? canvas.getBoundingClientRect()
      : { left: 0, top: 0 };
    return {
      x: x - canvasBox.left,
      y: y - canvasBox.top,
    };
  },
  canvasMouseDown: function (e) {
    console.log("canvasMouseDown");
    const loc = this.windowToCanvas(e.clientX, e.clientY);
    this.mouseDown = JSON.parse(JSON.stringify(loc));
    this.lastXY = JSON.parse(JSON.stringify(loc));
    this.dragging = true;
    this.setCursor();
    this.eraseRestoreLast();
  },
  canvasMouseMove: function (e) {
    console.log("canvasMouseMove");

    if (!canvas) {
      return false;
    }
    const { dragging, isOver } = this;
    const loc = this.windowToCanvas(e.clientX, e.clientY);
    const isInCanvas =
      loc.x > 0 && loc.x < canvas.width && loc.y > 0 && loc.y < canvas.height;
    isInCanvas ? (this.currentXY = JSON.parse(JSON.stringify(loc))) : null;

    if (dragging) {
      this.eraseRestoreLast();
    }
    if (isInCanvas) {
      this.lastXY = JSON.parse(JSON.stringify(loc));
      this.setCursor();
    }
  },
  canvasMouseUp: function (e) {
    console.log("canvasMouseUp");
    const { dragging } = this;
    if (dragging) {
      this.addHistory(this.canvas, this.ctx, HistoryTypes.EDIT);
    }
  },
  canvasMouseEnter: function () {
    console.log("canvasMouseEnter");
    this.isOver = true;
    this.setCursor();
  },
  canvasMouseLeave: function () {
    console.log("canvasMouseLeave");
    this.isOver = false;
    this.setCursor();
  },
  eraseRestoreLast: function () {
    const { activeOperate } = this;
    if (activeOperate === OperateTypes.ERASE) {
      this.eraseCircle(this.canvas, this.ctx, false);
      this.eraseRectangle(this.canvas, this.ctx, false);
      this.eraseCircle(
        this.offscreenCanvasEdit,
        this.offscreenContextEdit,
        true
      );
      // 移动快的时候可能两个点之间有距离，在两个点之间画一个四边形，四边形的两条边是圆的切线，用以清除两个点之间的图像
      this.eraseRectangle(
        this.offscreenCanvasEdit,
        this.offscreenContextEdit,
        true
      );
    } else {
      this.restoreCircle(this.canvas, this.ctx, false);
      this.restoreRectangle(this.canvas, this.ctx, false);
      this.restoreCircle(
        this.offscreenCanvasEdit,
        this.offscreenContextEdit,
        true
      );
      this.restoreRectangle(
        this.offscreenCanvasEdit,
        this.offscreenContextEdit,
        true
      );
    }
  },
  eraseCircle: function (canvas, ctx, isOffscreen) {
    const { eraseBrushSize, scale } = this;
    ctx.save();
    ctx.beginPath();

    if (isOffscreen) {
      ctx.arc(
        (this.lastXY.x - this.imgXY.x) / scale,
        (this.lastXY.y - this.imgXY.y) / scale,
        eraseBrushSize / scale / 2,
        0,
        2 * Math.PI,
        false
      );
    } else {
      ctx.arc(
        this.currentXY.x,
        this.currentXY.y,
        eraseBrushSize / 2,
        0,
        2 * Math.PI,
        false
      );
    }

    ctx.clip();
    // 擦除
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    ctx.restore();
  },
  getRectangle: function (x1, y1, x2, y2, r) {
    /**
     *  移动快的时候可能两个点之间有距离，在两个点之间画一个四边形，四边形的两条边是圆的切线，用以清除两个点之间的图像
     * (x1,y1) (x1,y1) 两个圆的圆心,r是半径，两个圆半径相等
     * 需要返回四边形的四个顶点，存在points中
     */
    const points = [];
    const angle = Math.atan((x2 - x1) / (y2 - y1));

    points[0] = {
      x: x1 + r * Math.cos(angle),
      y: y1 - r * Math.sin(angle),
    };
    points[1] = {
      x: x2 + r * Math.cos(angle),
      y: y2 - r * Math.sin(angle),
    };
    points[2] = {
      x: x2 - r * Math.cos(angle),
      y: y2 + r * Math.sin(angle),
    };
    points[3] = {
      x: x1 - r * Math.cos(angle),
      y: y1 + r * Math.sin(angle),
    };
    return points;
  },
  eraseRectangle: function (canvas, ctx, isOffscreen) {
    let points = [];

    ctx.save();
    ctx.beginPath();
    if (isOffscreen) {
      points = this.getRectangle(
        (this.lastXY.x - this.imgXY.x) / this.scale,
        (this.lastXY.y - this.imgXY.y) / this.scale,
        (this.currentXY.x - this.imgXY.x) / this.scale,
        (this.currentXY.y - this.imgXY.x) / this.scale,
        this.eraseBrushSize / this.scale / 2
      );
    } else {
      points = this.getRectangle(
        this.lastXY.x,
        this.lastXY.y,
        this.currentXY.x,
        this.currentXY.y,
        this.eraseBrushSize / 2
      );
    }
    this.drawRectangle(points, ctx);
    ctx.clip();
    // 擦除
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.restore();
  },
  drawRectangle: function (points, ctx) {
    ctx.moveTo(points[0].x, points[0].y);
    for (let i = 1; i < points.length; i++) {
      ctx.lineTo(points[i].x, points[i].y);
    }
    ctx.closePath();
    ctx.strokeStyle = "red";
    ctx.lineWidth = 1;
    ctx.stroke();
  },
  restoreRectangle: function (canvas, ctx, isOffscreen) {
    const { restoreBrushSize, scale } = this;

    if (this.canvas && this.ctx) {
      ctx.save();
      ctx.beginPath();
      let points = [];
      if (isOffscreen) {
        points = this.getRectangle(
          (this.lastXY.x - this.imgXY.x) / scale,
          (this.lastXY.y - this.imgXY.y) / scale,
          (this.currentXY.x - this.imgXY.x) / scale,
          (this.currentXY.y - this.imgXY.x) / scale,
          restoreBrushSize / scale / 2
        );
        this.drawRectangle(points, ctx);

        ctx.clip();
        // 从离屏幕canvas复制
        ctx.drawImage(this.imageOri, 0, 0, canvas.width, canvas.height);
      } else {
        points = this.getRectangle(
          this.lastXY.x,
          this.lastXY.y,
          this.currentXY.x,
          this.currentXY.y,
          restoreBrushSize / 2
        );
        this.drawRectangle(points, ctx);
        ctx.clip();
        // 从离屏幕canvas复制
        this.offscreenCanvasOri
          ? ctx.drawImage(this.offscreenCanvasOri, 0, 0)
          : null;
      }
      ctx.restore();
    }
  },

  restoreCircle: function (canvas, ctx, isOffscreen) {
    if (this.canvas && this.ctx) {
      const { restoreBrushSize, scale } = this;

      ctx.save();
      ctx.beginPath();

      if (isOffscreen) {
        ctx.arc(
          (this.lastXY.x - this.imgXY.x) / scale,
          (this.lastXY.y - this.imgXY.y) / scale,
          restoreBrushSize / scale / 2,
          0,
          2 * Math.PI,
          false
        );
        ctx.clip();
        // 从离屏幕canvas复制
        ctx.drawImage(this.imageOri, 0, 0, canvas.width, canvas.height);
      } else {
        ctx.arc(
          this.lastXY.x,
          this.lastXY.y,
          restoreBrushSize / 2,
          0,
          2 * Math.PI,
          false
        );
        ctx.clip();
        // 从离屏幕canvas复制
        this.offscreenCanvasOri
          ? ctx.drawImage(this.offscreenCanvasOri, 0, 0)
          : null;
      }

      ctx.restore();
    }
  },

  changeScale: function (type) {
    const { scale } = this;
    if (type === ScaleChangeType.ZOOMIN) {
      this.scale = Number((scale + 0.2).toFixed(1));
    } else {
      this.scale = Number((scale - 0.2).toFixed(1));
    }
    this.setScaleNum();
    this.drawScaleImage();
  },
  drawScaleImage: function () {
    const { scale } = this;

    if (this.canvas && this.ctx) {
      const w = canvas.width,
        h = canvas.height;
      const sw = scale * w,
        sh = scale * h;
      this.imgXY = {
        x: w / 2 - sw / 2,
        y: h / 2 - sh / 2,
      };
      // 用于记录原图的离屏canvas也需要对应放大缩小
      if (this.offscreenContextOri && this.offscreenCanvasOri) {
        this.offscreenContextOri.clearRect(
          0,
          0,
          this.canvas.width,
          this.canvas.height
        );
        this.offscreenContextOri.drawImage(
          this.imageOri,
          this.imgXY.x,
          this.imgXY.y,
          sw,
          sh
        );
      }
      if (this.offscreenCanvasEdit) {
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.ctx.drawImage(
          this.offscreenCanvasEdit,
          this.imgXY.x,
          this.imgXY.y,
          sw,
          sh
        );
      }
      this.addHistory(this.canvas, this.ctx, HistoryTypes.SCALE);
    }
  },
};
window.onload = function () {
  tool.init();
};
